Offline chatting using a ESP32 code

 #include <WiFi.h>

#include <DNSServer.h>

#include <WebServer.h>

#include <vector>


// Wi-Fi Configuration (Open Network)

const char* ssid = "FREE CHAT";


// DNS and Web Server Settings

const byte DNS_PORT = 53;

IPAddress apIP(192, 168, 4, 1);

DNSServer dnsServer;

WebServer server(80);


// Structure to store messages

struct Message {

  String sender;

  String text;

};

std::vector<Message> chatHistory;

const size_t MAX_MESSAGES = 15; // Image support ke liye memory limit ko 15 kiya taaki crash na ho


// JSON Escape helper to handle special characters and long strings safely

String escapeJSON(String input) {

  input.replace("\\", "\\\\");

  input.replace("\"", "\\\"");

  input.replace("\n", "\\n");

  input.replace("\r", "");

  return input;

}


// HTML, CSS, aur JavaScript (With Image Compression & Upload Feature)

const char HTML_INDEX[] PROGMEM = R"=====(

<!DOCTYPE html>

<html lang='en'>

<head>

    <meta charset='UTF-8'>

    <meta name='viewport' content='width=device-width, initial-scale=1.0'>

    <title>Free Chat Hub</title>

    <style>

        * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }

        body {

            background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);

            color: #f8fafc;

            display: flex;

            flex-direction: column;

            height: 100vh;

            overflow: hidden;

        }

        header {

            background: rgba(255, 255, 255, 0.03);

            backdrop-filter: blur(10px);

            padding: 15px;

            text-align: center;

            border-bottom: 1px solid rgba(255, 255, 255, 0.1);

            box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);

            display: flex;

            justify-content: center;

            align-items: center;

            gap: 10px;

        }

        .pulse {

            width: 10px;

            height: 10px;

            background: #10b981;

            border-radius: 50%;

            box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);

            animation: pulse 1.5s infinite;

        }

        @keyframes pulse {

            0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }

            70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }

            100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }

        }

        #chat-container {

            flex: 1;

            overflow-y: auto;

            padding: 20px;

            display: flex;

            flex-direction: column;

            gap: 12px;

        }

        .msg {

            max-width: 75%;

            padding: 12px 16px;

            border-radius: 16px;

            font-size: 15px;

            line-height: 1.4;

            animation: fadeIn 0.3s ease-out forwards;

            opacity: 0;

            transform: translateY(10px);

            box-shadow: 0 4px 15px rgba(0,0,0,0.2);

        }

        @keyframes fadeIn {

            to { opacity: 1; transform: translateY(0); }

        }

        .msg.self {

            align-self: flex-end;

            background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);

            color: white;

            border-bottom-right-radius: 4px;

        }

        .msg.other {

            align-self: flex-start;

            background: rgba(255, 255, 255, 0.07);

            border-bottom-left-radius: 4px;

            border: 1px solid rgba(255, 255, 255, 0.05);

        }

        .meta {

            font-size: 11px;

            opacity: 0.6;

            margin-bottom: 4px;

            font-weight: bold;

        }

        footer {

            padding: 15px;

            background: rgba(255, 255, 255, 0.02);

            backdrop-filter: blur(10px);

            border-top: 1px solid rgba(255, 255, 255, 0.1);

            display: flex;

            flex-direction: column;

            gap: 10px;

        }

        .input-row { display: flex; gap: 10px; align-items: center; }

        input {

            background: rgba(255, 255, 255, 0.05);

            border: 1px solid rgba(255, 255, 255, 0.1);

            padding: 12px;

            border-radius: 12px;

            color: white;

            outline: none;

            font-size: 15px;

            transition: all 0.3s;

        }

        input:focus {

            border-color: #6366f1;

            background: rgba(255, 255, 255, 0.08);

            box-shadow: 0 0 10px rgba(99, 102, 241, 0.2);

        }

        #name-input { width: 100px; }

        #msg-input { flex: 1; }

        button {

            background: #6366f1;

            color: white;

            border: none;

            padding: 12px 18px;

            border-radius: 12px;

            cursor: pointer;

            font-weight: bold;

            font-size: 15px;

            transition: all 0.2s;

        }

        button:hover { background: #4f46e5; transform: scale(1.02); }

        button:active { transform: scale(0.98); }

        .btn-img { background: #e11d48; }

        .btn-img:hover { background: #be123c; }

        .chat-image { max-width: 100%; max-height: 150px; border-radius: 8px; margin-top: 5px; display: block; }

    </style>

</head>

<body>


    <header>

        <div class="pulse"></div>

        <h2>FREE CHAT HUB</h2>

    </header>


    <div id="chat-container"></div>


    <footer>

        <div class="input-row">

            <input type="text" id="name-input" placeholder="Name..." maxlength="10">

            <input type="text" id="msg-input" placeholder="Type a message..." maxlength="100">

           

            <!-- Hidden File Input for Image -->

            <input type="file" id="file-input" accept="image/*" style="display:none" onchange="compressAndSendImage(event)">

            <button class="btn-img" onclick="document.getElementById('file-input').click()">📷</button>

           

            <button onclick="sendMessage()">Send</button>

        </div>

    </footer>


    <script>

        if(localStorage.getItem('chat_user')) {

            document.getElementById('name-input').value = localStorage.getItem('chat_user');

        } else {

            document.getElementById('name-input').value = "User_" + Math.floor(Math.random() * 900 + 100);

        }


        let lastMessageCount = 0;


        // Messages load karne ka function

        async function loadMessages() {

            try {

                let res = await fetch('/getMessages');

                let data = await res.json();

                let container = document.getElementById('chat-container');

                let currentName = document.getElementById('name-input').value.trim();

               

                if (data.length !== lastMessageCount) {

                    container.innerHTML = '';

                    data.forEach(msg => {

                        let isSelf = msg.sender === currentName;

                        let div = document.createElement('div');

                        div.className = `msg ${isSelf ? 'self' : 'other'}`;

                       

                        // Check karein ki message normal text hai ya image data URL

                        let isImg = msg.text.startsWith('data:image/');

                        let msgContent = isImg ? `<img src="${msg.text}" class="chat-image" />` : escapeHTML(msg.text);

                       

                        div.innerHTML = `<div class="meta">${escapeHTML(msg.sender)}</div><div>${msgContent}</div>`;

                        container.appendChild(div);

                    });

                    container.scrollTop = container.scrollHeight;

                    lastMessageCount = data.length;

                }

            } catch (e) { console.log("Connection lost"); }

        }


        // Text Message send karne ka function (POST Request)

        async function sendMessage() {

            let nameField = document.getElementById('name-input');

            let msgField = document.getElementById('msg-input');

            let name = nameField.value.trim();

            let text = msgField.value.trim();


            if(!name || !text) return;

            localStorage.setItem('chat_user', name);

            msgField.value = '';

           

            let bodyData = new URLSearchParams();

            bodyData.append('sender', name);

            bodyData.append('text', text);


            await fetch('/sendMessage', {

                method: 'POST',

                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },

                body: bodyData

            });

            loadMessages();

        }


        // Image compress karne aur use send karne ka main function

        function compressAndSendImage(event) {

            let file = event.target.files[0];

            if (!file) return;


            let name = document.getElementById('name-input').value.trim();

            if (!name) { alert("Pehle apna Name likhein!"); return; }

            localStorage.setItem('chat_user', name);


            let reader = new FileReader();

            reader.onload = function(e) {

                let img = new Image();

                img.onload = async function() {

                    let canvas = document.createElement('canvas');

                    let max_size = 150; // Image ki maximum width ya height 150px hogi

                    let width = img.width;

                    let height = img.height;


                    if (width > height) {

                        if (width > max_size) { height *= max_size / width; width = max_size; }

                    } else {

                        if (height > max_size) { width *= max_size / height; height = max_size; }

                    }


                    canvas.width = width;

                    canvas.height = height;

                    let ctx = canvas.getContext('2d');

                    ctx.drawImage(img, 0, 0, width, height);


                    // Image ko low quality (0.15) JPEG me convert karein (Super Low Size)

                    let base64Img = canvas.toDataURL('image/jpeg', 0.15);

                   

                    // ESP32 ko POST request se bhejhein

                    let bodyData = new URLSearchParams();

                    bodyData.append('sender', name);

                    bodyData.append('text', base64Img);


                    await fetch('/sendMessage', {

                        method: 'POST',

                        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },

                        body: bodyData

                    });

                   

                    // Input clear karein taaki same image dobara select ho sake

                    document.getElementById('file-input').value = '';

                    loadMessages();

                }

                img.src = e.target.result;

            }

            reader.readAsDataURL(file);

        }


        document.getElementById('msg-input').addEventListener('keypress', function(e) {

            if (e.key === 'Enter') sendMessage();

        });


        function escapeHTML(str) {

            return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");

        }


        setInterval(loadMessages, 1000);

        loadMessages();

    </script>

</body>

</html>

)=====";


// Captive Portal Handling

void handleNotFound() {

  String host = server.hostHeader();

  if (host != "192.168.4.1" && host != "localhost") {

    server.sendHeader("Location", "http://192.168.4.1/", true);

    server.send(302, "text/plain", "");

  } else {

    server.send(200, "text/html", HTML_INDEX);

  }

}


void setup() {

  Serial.begin(115200);

 

  // 1. AP Mode Setup

  WiFi.mode(WIFI_AP);

  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));

  WiFi.softAP(ssid);

 

  Serial.println("Wi-Fi 'FREE CHAT' Started!");

  Serial.print("IP Address: ");

  Serial.println(WiFi.softAPIP());


  // 2. DNS Server Setup

  dnsServer.setErrorReplyCode(DNSReplyCode::NoError);

  dnsServer.start(DNS_PORT, "*", apIP);


  // 3. Web Server Routes

  server.on("/", HTTP_GET, []() {

    server.send(200, "text/html", HTML_INDEX);

  });


  // API: Fetch chat log

  server.on("/getMessages", HTTP_GET, []() {

    String json = "[";

    for (size_t i = 0; i < chatHistory.size(); i++) {

      json += "{\"sender\":\"" + escapeJSON(chatHistory[i].sender) + "\",\"text\":\"" + escapeJSON(chatHistory[i].text) + "\"}";

      if (i < chatHistory.size() - 1) json += ",";

    }

    json += "]";

    server.send(200, "application/json", json);

  });


  // API: Receive new message/image via POST

  server.on("/sendMessage", HTTP_POST, []() {

    if (server.hasArg("sender") && server.hasArg("text")) {

      Message msg;

      msg.sender = server.arg("sender");

      msg.text = server.arg("text");

     

      if (chatHistory.size() >= MAX_MESSAGES) {

        chatHistory.erase(chatHistory.begin()); // Save RAM

      }

      chatHistory.push_back(msg);

      server.send(200, "text/plain", "OK");

    } else {

      server.send(400, "text/plain", "Bad Request");

    }

  });


  server.onNotFound(handleNotFound);

  server.begin();

  Serial.println("Web Server Started!");

}


void loop() {

  dnsServer.processNextRequest();

  server.handleClient();

  delay(2);

}

Comments

Popular posts from this blog

ESP32 Wi-Fi Extender Code

WiFi Remote Control RC Car Code For ESP32